/*
 * Encoder is the base class for all GSM encoding.
 * Copyright (C) 1999  Christopher Edwards
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
package javaforce.codec.gsm;

import java.io.*;
import java.lang.*;

public class GSMEncoder
        extends Thread {

  /* Every Encoder has a state through completion */
  private Gsm_State g_s = new Gsm_State();
  private Long_term lg_term_Obj = new Long_term();
  private Lpc lpc_Obj = new Lpc();
  private Rpe rpe_Obj = new Rpe();
  private Short_term sh_term_Obj = new Short_term();

  /* [0..7] LAR coefficients              OUT     */
  private short LARc[] = new short[8];
  /* [0..3] LTP lag 		            OUT     */
  private short Nc[] = new short[4];
  /* [0..3] coded LTP gain                OUT     */
  private short Mc[] = new short[4];
  /* [0..3] RPE grid selection            OUT     */
  private short bc[] = new short[4];
  /* [0..3] Coded maximum amplitude       OUT     */
  private short xmaxc[] = new short[4];
  /* [13*4] normalized RPE samples        OUT     */
  private short xmc[] = new short[13 * 4];

  /* Reads 160 bytes */
  private int[] input_signal = new int[160];
  /* [0..159]  OUT */
 /* Writes 33 bytes */
  private byte[] frame = new byte[Gsm_Def.FRAME_SIZE];

  /**
   * Encoder class constructor.
   */
  public GSMEncoder() {
  }

  /**
   * Remove the header info from the stream and verifies the file type. As
   * defined by the NeXT/Sun audio file format U-law (.au). For more info see
   * the README file. <br><br>
   * Note: Most of this info is not needed to reproduce the sound file after
   * encoding. All that is needed is the magic number and the sampling rate to
   * reproduce the sound file during decoding.
   *
   * @param in Strip the header from a Sun/Next formated sound stream.
   */
  public static void stripAUHeader(InputStream in)
          throws Exception {
    DataInputStream input = new DataInputStream((InputStream) in);

    /* Just read these bits from the stream and do nothing with them */
    int magic = input.readInt();
    /* magic number SND_MAGIC ((int)0x2e736e64),
						 * which equals ".snd".)
     */
    input.readInt();
    /* offset or pointer to the data */
    input.readInt();
    /* number of bytes of data */
    int dataFormat = input.readInt();
    /* the data format code */
    int sampleRate = input.readInt();
    /* the sampling rate = ~8000 samples/sec. */
    input.readInt();
    /* the number of channels */
    input.readChar();
    /* optional text information - 4 chars */
    input.readChar();
    input.readChar();
    input.readChar();

    if (magic != 0x2E736E64) // ".snd" in ASCII
    {
      // throw new GsmException("AuFile wrong Magic Number");
    } else if (dataFormat != 1) // 8-Bit mu-Law
    {
      // throw new GsmException("AuFile not 8-bit Mu-Law");
    } else if (sampleRate != 8000) // 8kHz
    {
      // throw new GsmException("AuFile not 8kHz");
    }
  }

  /**
   * Encode the specified file.
   * <br>This method calls the <code>stripAUHeader</code> method for you.<br>
   * stripAUHeader will verify file type.
   *
   * @param input_file The name of the file to encode.
   * @param output_file The name of the GSM encoded file.
   */
  public void encode(String input_file, String output_file)
          throws Exception {

    File arg1 = new File(input_file);
    if (!arg1.exists() || !arg1.isFile() || !arg1.canRead()) {
      throw new IOException("File : "
              + input_file + "\ndoes not exist or cannot be read.");
    }

    FileInputStream from = null;
    FileOutputStream to = null;
    try {
      from = new FileInputStream(input_file);
      to = new FileOutputStream(output_file);

      // Remove the header. It gets mangled by the encoding.
      stripAUHeader(from);

      int check_stream = 0;

      // Read bytes till EOF.
      while ((check_stream = ulaw_input(from)) > 0) {
        //System.out.println("Entering Native method.");
        gsm_encode();

        // Need to do some error check here. Update ulaw_output.
        ulaw_output(to);           // Write bytes.
      }

    } catch (Exception e) {
      throw new Exception("Encoder: " + e.getMessage());
    } finally {
      if (from != null) {
        try {
          from.close();
        } catch (IOException e) {
          throw new IOException("Encoder: " + e.getMessage());
        }
      }
      if (to != null) {
        try {
          to.close();
        } catch (IOException e) {
          throw new IOException("Encoder: " + e.getMessage());
        }
      }
    }
  }

  /**
   * Encode the specified InputStream.
   *
   * @param input The stream to encode.
   * @param output_file The name of the GSM encoded file.
   */
  public void encode(InputStream input, String output_file)
          throws IOException {
    FileOutputStream to = null;
    try {
      to = new FileOutputStream(output_file);

      int check_stream = 0;

      // Read bytes till EOF.
      while ((check_stream = ulaw_input(input)) > 0) {
        gsm_encode();

        // Need to do some error check here. Update ulaw_output.
        ulaw_output(to);           // Write bytes.
      }

    } catch (IOException e) {
      throw new IOException("Encoder: " + e.getMessage());
    } finally {
      if (to != null) {
        try {
          to.close();
        } catch (IOException e) {
          throw new IOException("Encoder: " + e.getMessage());
        }
      }
    }
  }

  /**
   * Encodes a block of data.
   *
   * @param asBuffer	an 160-element array with the data to encode int PCM 16 bit
   * format.
   *
   * @param abFrame	the encoded GSM frame (33 bytes).
   */
  public void encode(short[] asBuffer, byte[] abFrame) {
    for (int i = 0; i < 160; i++) {
      input_signal[i] = asBuffer[i];
    }
    gsm_encode();
    System.arraycopy(frame, 0, abFrame, 0, frame.length);
  }

  /**
   * Read 160 bytes from a U-law stream and set up the input_signal array.
   */
  private int ulaw_input(InputStream in)
          throws IOException {
    int c = 0;
    int i = 0;

    for (i = 0; i < input_signal.length && ((c = in.read()) != -1); i++) {
      if (c < 0) {
        throw new IOException("Encoder ulaw_input: Corrupt InputStream.");
      } else {
        input_signal[i] = u2s[c];
      }
    }
    return (i);
  }

  private void gsm_encode() {
    int index = 0;

    Gsm_Coder_java();

    frame[index++] = (byte) (((0xD) << 4) /* 1 */
            | ((LARc[0] >> 2) & 0xF));
    frame[index++] = (byte) (((LARc[0] & 0x3) << 6) /* 2 */
            | (LARc[1] & 0x3F));
    frame[index++] = (byte) (((LARc[2] & 0x1F) << 3) /* 3 */
            | ((LARc[3] >> 2) & 0x7));
    frame[index++] = (byte) (((LARc[3] & 0x3) << 6) /* 4 */
            | ((LARc[4] & 0xF) << 2)
            | ((LARc[5] >> 2) & 0x3));
    frame[index++] = (byte) (((LARc[5] & 0x3) << 6) /* 5 */
            | ((LARc[6] & 0x7) << 3)
            | (LARc[7] & 0x7));
    frame[index++] = (byte) (((Nc[0] & 0x7F) << 1) /* 6 */
            | ((bc[0] >> 1) & 0x1));
    frame[index++] = (byte) (((bc[0] & 0x1) << 7) /* 7 */
            | ((Mc[0] & 0x3) << 5)
            | ((xmaxc[0] >> 1) & 0x1F));
    frame[index++] = (byte) (((xmaxc[0] & 0x1) << 7) /* 8 */
            | ((xmc[0] & 0x7) << 4)
            | ((xmc[1] & 0x7) << 1)
            | ((xmc[2] >> 2) & 0x1));
    frame[index++] = (byte) (((xmc[2] & 0x3) << 6) /* 9 */
            | ((xmc[3] & 0x7) << 3)
            | (xmc[4] & 0x7));
    frame[index++] = (byte) (((xmc[5] & 0x7) << 5) /* 10 */
            | ((xmc[6] & 0x7) << 2)
            | ((xmc[7] >> 1) & 0x3));
    frame[index++] = (byte) (((xmc[7] & 0x1) << 7) /* 11 */
            | ((xmc[8] & 0x7) << 4)
            | ((xmc[9] & 0x7) << 1)
            | ((xmc[10] >> 2) & 0x1));
    frame[index++] = (byte) ((((xmc[10] & 0x3) << 6) /* 12 */
            | ((xmc[11] & 0x7) << 3)
            | (xmc[12] & 0x7)));
    frame[index++] = (byte) (((Nc[1] & 0x7F) << 1) /* 13 */
            | ((bc[1] >> 1) & 0x1));
    frame[index++] = (byte) (((bc[1] & 0x1) << 7) /* 14 */
            | ((Mc[1] & 0x3) << 5)
            | ((xmaxc[1] >> 1) & 0x1F));
    frame[index++] = (byte) (((xmaxc[1] & 0x1) << 7) /* 15 */
            | ((xmc[13] & 0x7) << 4)
            | ((xmc[14] & 0x7) << 1)
            | ((xmc[15] >> 2) & 0x1));
    frame[index++] = (byte) (((xmc[15] & 0x3) << 6)
            | ((xmc[16] & 0x7) << 3)
            | (xmc[17] & 0x7));
    frame[index++] = (byte) (((xmc[18] & 0x7) << 5)
            | ((xmc[19] & 0x7) << 2)
            | ((xmc[20] >> 1) & 0x3));
    frame[index++] = (byte) (((xmc[20] & 0x1) << 7)
            | ((xmc[21] & 0x7) << 4)
            | ((xmc[22] & 0x7) << 1)
            | ((xmc[23] >> 2) & 0x1));
    frame[index++] = (byte) (((xmc[23] & 0x3) << 6)
            | ((xmc[24] & 0x7) << 3)
            | (xmc[25] & 0x7));
    frame[index++] = (byte) (((Nc[2] & 0x7F) << 1) /* 20 */
            | ((bc[2] >> 1) & 0x1));
    frame[index++] = (byte) (((bc[2] & 0x1) << 7)
            | ((Mc[2] & 0x3) << 5)
            | ((xmaxc[2] >> 1) & 0x1F));
    frame[index++] = (byte) (((xmaxc[2] & 0x1) << 7)
            | ((xmc[26] & 0x7) << 4)
            | ((xmc[27] & 0x7) << 1)
            | ((xmc[28] >> 2) & 0x1));
    frame[index++] = (byte) (((xmc[28] & 0x3) << 6)
            | ((xmc[29] & 0x7) << 3)
            | (xmc[30] & 0x7));
    frame[index++] = (byte) (((xmc[31] & 0x7) << 5)
            | ((xmc[32] & 0x7) << 2)
            | ((xmc[33] >> 1) & 0x3));
    frame[index++] = (byte) (((xmc[33] & 0x1) << 7)
            | ((xmc[34] & 0x7) << 4)
            | ((xmc[35] & 0x7) << 1)
            | ((xmc[36] >> 2) & 0x1));
    frame[index++] = (byte) (((xmc[36] & 0x3) << 6)
            | ((xmc[37] & 0x7) << 3)
            | (xmc[38] & 0x7));
    frame[index++] = (byte) (((Nc[3] & 0x7F) << 1)
            | ((bc[3] >> 1) & 0x1));
    frame[index++] = (byte) (((bc[3] & 0x1) << 7)
            | ((Mc[3] & 0x3) << 5)
            | ((xmaxc[3] >> 1) & 0x1F));
    frame[index++] = (byte) (((xmaxc[3] & 0x1) << 7)
            | ((xmc[39] & 0x7) << 4)
            | ((xmc[40] & 0x7) << 1)
            | ((xmc[41] >> 2) & 0x1));
    frame[index++] = (byte) (((xmc[41] & 0x3) << 6) /* 30 */
            | ((xmc[42] & 0x7) << 3)
            | (xmc[43] & 0x7));
    frame[index++] = (byte) (((xmc[44] & 0x7) << 5) /* 31 */
            | ((xmc[45] & 0x7) << 2)
            | ((xmc[46] >> 1) & 0x3));
    frame[index++] = (byte) (((xmc[46] & 0x1) << 7) /* 32 */
            | ((xmc[47] & 0x7) << 4)
            | ((xmc[48] & 0x7) << 1)
            | ((xmc[49] >> 2) & 0x1));
    frame[index++] = (byte) (((xmc[49] & 0x3) << 6) /* 33 */
            | ((xmc[50] & 0x7) << 3)
            | (xmc[51] & 0x7));
  }

  private void Gsm_Coder_java() {
    int xmc_point = 0;
    int Nc_bc_index = 0;
    int xmaxc_Mc_index = 0;
    int dp_dpp_point_dp0 = 120;

    short[] ep = new short[40];
    short[] e = new short[50];
    short[] so = new short[160];

    Gsm_Preprocess(so);
    lpc_Obj.Gsm_LPC_Analysis(so, LARc);
    sh_term_Obj.Gsm_Short_Term_Analysis_Filter(g_s, LARc, so);

    short[] dp = g_s.getDp0();
    short[] dpp = dp;

    for (int k = 0; k <= 3; k++, xmc_point += 13) {
      lg_term_Obj.Gsm_Long_Term_Predictor(
              so, /* d    [0..39] IN    */
              k * 40, /* so   entry point   */
              e, /* e+5  [0..39] OUT   */
              dp, /* Referance to Gsm_State dp0 */
              dpp, /* Referance to Gsm_State dp0 */
              dp_dpp_point_dp0, /* Where to start the dp0 ref */
              Nc, /* [0..3] coded LTP gain   	 OUT */
              bc, /* [0..3] RPE grid selection    OUT */
              Nc_bc_index++ /* The current referance point for Nc & bc */
      );

      rpe_Obj.Gsm_RPE_Encoding(e, /* e + 5 ][0..39][ IN/OUT */
              xmaxc, /* [0..3] Coded maximum amplitude  OUT     */
              Mc, /* [0..3] coded LTP gain           OUT     */
              xmaxc_Mc_index++, /* The current referance point   */
              xmc, /* [13*4] normalized RPE samples   OUT     */
              xmc_point /* The current referance point for xmc   */);

      for (int i = 0; i <= 39; i++) {
        dp[i + dp_dpp_point_dp0] = Add.GSM_ADD(e[5 + i],
                dpp[i + dp_dpp_point_dp0]);
      }

      g_s.setDp0(dp);
      dp_dpp_point_dp0 += 40;
    }

    for (int i = 0; i < 120; i++) {
      g_s.setDp0Indexed(i, g_s.getDp0Indexed((160 + i)));
    }
  }

  private void Gsm_Preprocess(short[] so) /* [0..159] 	IN/OUT	*/
          throws IllegalArgumentException {
    int index = 0, so_index = 0;

    short z1 = g_s.getZ1();
    int L_z2 = g_s.getL_z2();
    int mp = g_s.getMp();

    short s1 = 0, msp = 0, lsp = 0, temp = 0, SO = 0;
    int L_s2 = 0, L_temp = 0;
    int k = 160;

    while (k != 0) {
      k--;

      /*  4.2.1   Downscaling of the input signal
       */
      SO = (short) (Add.SASR((short) input_signal[index++], (short) 3) << 2);

      if (!(SO >= -0x4000)) {
        /* downscaled by     */
        throw new IllegalArgumentException("Gsm_Preprocess: SO = "
                + SO + " is out of range. Sould be >= -0x4000 ");
      }

      if (!(SO <= 0x3FFC)) {
        /* previous routine. */
        throw new IllegalArgumentException("Gsm_Preprocess: SO = "
                + SO + " is out of range. Sould be <= 0x3FFC ");
      }


      /*  4.2.2   Offset compensation
			 *
			 *  This part implements a high-pass filter and requires extended
			 *  arithmetic precision for the recursive part of this filter.
			 *  The input of this procedure is the array so[0...159] and the
			 *  output the array sof[ 0...159 ].
       */

 /*   Compute the non-recursive part
       */
      s1 = (short) (SO - z1);
      /* s1 = gsm_sub( *so, z1 ); */
      z1 = SO;

      if (s1 == Gsm_Def.MIN_WORD) {
        throw new IllegalArgumentException("Gsm_Preprocess: s1 = "
                + s1 + " is out of range. ");
      }

      /*   Compute the recursive part
       */
      L_s2 = s1;
      L_s2 <<= 15;

      /*   Execution of a 31 bv 16 bits multiplication
       */
      msp = Add.SASR(L_z2, 15);

      /* gsm_L_sub(L_z2,(msp<<15)); */
      lsp = (short) (L_z2 - ((int) (msp << 15)));

      L_s2 += Add.GSM_MULT_R(lsp, (short) 32735);
      L_temp = (int) msp * 32735;
      /* GSM_L_MULT(msp,32735) >> 1;*/
      L_z2 = Add.GSM_L_ADD(L_temp, L_s2);

      /*    Compute sof[k] with rounding
       */
      L_temp = Add.GSM_L_ADD(L_z2, 16384);

      /*   4.2.3  Preemphasis
       */
      msp = Add.GSM_MULT_R((short) mp, (short) -28180);
      mp = Add.SASR(L_temp, 15);
      so[so_index++] = Add.GSM_ADD((short) mp, msp);
    }
    g_s.setZ1(z1);
    g_s.setL_z2(L_z2);
    g_s.setMp(mp);
  }

  /* Write the encoded bytes to the stream. */
  private void ulaw_output(FileOutputStream out)
          throws IOException {
    int i = 0;
    byte output = 0;

    for (i = 0; i < frame.length; i++) {
      out.write(frame[i]);
    }
  }

  /**
   * Used for debugging.
   *
   * @param g_s The Gsm_State object to be viewed.
   */
  private void dump_Gsm_State(Gsm_State g_s) {
    g_s.dump_Gsm_State();
  }

  /*
	 * This is the encoding matrix.
	 *
	 * Java does not have an unsigned short, ie. 16 bits, so
	 * I will use the upper 16 bits of the integer. This wastes
	 * a little memory although I do not think it will cause a
	 * problem.
   */
  private static final int u2s[]
          = {
            33280, 34308, 35336, 36364, 37393, 38421, 39449, 40477,
            41505, 42534, 43562, 44590, 45618, 46647, 47675, 48703,
            49474, 49988, 50503, 51017, 51531, 52045, 52559, 53073,
            53587, 54101, 54616, 55130, 55644, 56158, 56672, 57186,
            57572, 57829, 58086, 58343, 58600, 58857, 59114, 59371,
            59628, 59885, 60142, 60399, 60656, 60913, 61171, 61428,
            61620, 61749, 61877, 62006, 62134, 62263, 62392, 62520,
            62649, 62777, 62906, 63034, 63163, 63291, 63420, 63548,
            63645, 63709, 63773, 63838, 63902, 63966, 64030, 64095,
            64159, 64223, 64287, 64352, 64416, 64480, 64544, 64609,
            64657, 64689, 64721, 64753, 64785, 64818, 64850, 64882,
            64914, 64946, 64978, 65010, 65042, 65075, 65107, 65139,
            65163, 65179, 65195, 65211, 65227, 65243, 65259, 65275,
            65291, 65308, 65324, 65340, 65356, 65372, 65388, 65404,
            65416, 65424, 65432, 65440, 65448, 65456, 65464, 65472,
            65480, 65488, 65496, 65504, 65512, 65520, 65528, 0,
            32256, 31228, 30200, 29172, 28143, 27115, 26087, 25059,
            24031, 23002, 21974, 20946, 19918, 18889, 17861, 16833,
            16062, 15548, 15033, 14519, 14005, 13491, 12977, 12463,
            11949, 11435, 10920, 10406, 9892, 9378, 8864, 8350,
            7964, 7707, 7450, 7193, 6936, 6679, 6422, 6165,
            5908, 5651, 5394, 5137, 4880, 4623, 4365, 4108,
            3916, 3787, 3659, 3530, 3402, 3273, 3144, 3016,
            2887, 2759, 2630, 2502, 2373, 2245, 2116, 1988,
            1891, 1827, 1763, 1698, 1634, 1570, 1506, 1441,
            1377, 1313, 1249, 1184, 1120, 1056, 992, 927,
            879, 847, 815, 783, 751, 718, 686, 654,
            622, 590, 558, 526, 494, 461, 429, 397,
            373, 357, 341, 325, 309, 293, 277, 261,
            245, 228, 212, 196, 180, 164, 148, 132,
            120, 112, 104, 96, 88, 80, 72, 64,
            56, 48, 40, 32, 24, 16, 8, 0
          };
}
